/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cz.dusa.switco;

import java.util.ArrayList;
import java.util.List;

/**
 * Sum of more convex cost functions can be evaluated by this class.
 * @author vdusa
 */
public class ConvexCostFunctionSum implements ConvexCostFunctionListener {
    private List<ConvexCostFunction> convexCostFunctions = new ArrayList<ConvexCostFunction>();
    private QuadraticCostFunction quadraticCostFunction = new QuadraticCostFunction(0, 0, 0);
    
    Double[] argMinCache;

    /**
     * Creates an empty sum of convex functions. To add a convex cost function
     * see {@link #addCostFunction(cz.dusa.switco.ConvexCostFunction)}
     */
    public ConvexCostFunctionSum() {}
       
    /**
     * Creates a new sum of convex cost functions of given tasks.
     * @param tasks containing cost functions.
     */
    public ConvexCostFunctionSum(List<Task> tasks) {
        for(Task t: tasks){
            addCostFunction(t.getCostFunction());
        }
    }

    /**
     * Adds a new convex cost function to the sum.
     * @param convexCostFunction to be added
     */
    final void addCostFunction(ConvexCostFunction convexCostFunction) {
        if(convexCostFunction instanceof QuadraticCostFunction){
            quadraticCostFunction.add((QuadraticCostFunction)convexCostFunction);
        } else{
            convexCostFunctions.add(convexCostFunction);
        }
        convexCostFunction.addConvexCostFunctionListener(this);
        clearCache();
    }
    
    /**
     * Evaluates the sum of convex cost functions in the given time point t.
     * @param t time point to be the cost function evaluated in.
     * @return value of the sum of convex cost functions in the time point t.
     * @throws cz.dusa.switco.EvaluationException 
     */
    public Double evaluate(Double t) throws cz.dusa.switco.EvaluationException{
        double result = 0;

        for(ConvexCostFunction f: convexCostFunctions){
            result += f.evaluate(t);
        }

        result += quadraticCostFunction.evaluate(t);

        return result;
    }

    /**
     * Clears the cache of the calculated argument of minimum of th convex cost function sum.
     * For more informatoin see {@link #getArgMin()}
     */
    private void clearCache(){
        argMinCache = null;
    }
    
    /**
     * For more information see {@link #getArgMin()}
     * @return cached argument of minimum of the convex cost function sum.
     */
    private Double[] getArgMinCache(){
        return argMinCache;
    }
    
    private Double[] getArgMinCalculated(){
        GlobalSettings globalSettingsInstance = GlobalSettings.getInstance();
        Double argMinLeft;
        Double argMinRight;

        if(convexCostFunctions.isEmpty()){
            //calculate only the specific functions it is faster
                argMinLeft = quadraticCostFunction.getArgMin();
                argMinRight = argMinLeft;
        } else {           
            Double a = globalSettingsInstance.getMinT();
            Double c = globalSettingsInstance.getMaxT();
            Double b = (a+c)/1.9;

            //we determine some ArgMin of the function (function with plateau can have more than one argmin)
            Double argMin = getArgMin(a,b,c);

            //we explore the neighbourhood of the argmin and we will extend the argmin to both directions
            //left argmin lies between a and argMin:
            Double leftLimit = new Double(a);
            argMinLeft = new Double(argMin);
            while (argMinLeft - leftLimit > globalSettingsInstance.getPrecisionLimit()/10){
                if (evaluate(leftLimit) > evaluate(argMinLeft)){
                    leftLimit = leftLimit + (argMinLeft - leftLimit) / 2.0;
                } else {
                    argMinLeft = argMinLeft - (argMinLeft - leftLimit) / 2.0;
                }
            }

            Double rightLimit = new Double(c);
            argMinRight = new Double(argMin);
            while (rightLimit - argMinRight > globalSettingsInstance.getPrecisionLimit()/10){
                if (evaluate(rightLimit) > evaluate(argMinRight)){
                    rightLimit = rightLimit - (rightLimit - argMinRight) / 2.0;
                } else {
                    argMinRight = argMinRight + (rightLimit - argMinRight) / 2.0;
                }
            }
        }

        return new Double[]{globalSettingsInstance.roundToPrecisionLimit(argMinLeft), globalSettingsInstance.roundToPrecisionLimit(argMinRight)};    
    }
    

    /**
     * @return If there is no cached argument of minimum of the convex cost function sum, it will calculate it and 
     * save it to the cache. If there is a result in cache already, the cached result is returned. Use {@link #clearCache()} 
     * to clear the cached result and to force calculate the value newly during next call of {@link #getArgMin()}.
     */
    public Double[] getArgMin() {               
        if (getArgMinCache() == null){
            argMinCache = getArgMinCalculated();
        } 
        
        return getArgMinCache();
        
    }

     /**
     * Calculates argument of minimum of the given sum of convex functions lying between a and b. 
     * @param a left border for the searched minimum
     * @param b
     * @param c right border for the searched minimum
     * @return argument of minimum of the sum of convex functions
     */
    private Double getArgMin(Double a, Double b, Double c){
        while (!(c-a < GlobalSettings.getInstance().getPrecisionLimit()/10)){
            Double w = (b-a) / (c-a);

            if (b-a > c-b){
                Double d = b - w*(b-a);
                if(evaluate(d) < evaluate(b)){
                    //return getArgMin(a, d, b);
                    c = b;
                    b = d;                    
                } else{
                    //return getArgMin(d, b, c);
                    a = d;
                }
            } else{
                Double d = b + w*(c-b);
                if(evaluate(b) < evaluate(d)){
                    //return getArgMin(a, b, d);
                    c = d;
                } else {
                    //return getArgMin(b, d, c);
                    a = b;
                    b = d;                            
                }
            }
        }
        
        return GlobalSettings.getInstance().roundToPrecisionLimit(c);
    }
    
//    private Double getArgMin(Double a, Double b, Double c){
//        if (c-a < GlobalSettings.getInstance().getPrecisionLimit()/10){
//            return GlobalSettings.getInstance().roundToPrecisionLimit(c);
//        }
//
//        Double w = (b-a) / (c-a);
//
//        if (b-a > c-b){
//            Double d = b - w*(b-a);
//            if(evaluate(d) < evaluate(b)){
//                return getArgMin(a, d, b);
//            } else{
//                return getArgMin(d, b, c);
//            }
//        } else{
//            Double d = b + w*(c-b);
//            if(evaluate(b) < evaluate(d)){
//                return getArgMin(a, b, d);
//            } else {
//                return getArgMin(b, d, c);
//            }
//        }
//    }

    /**
     * Convex cost function sum listens if some of the functions in the sum has been 
     * changed. If yes, the cache must be cleared and a new calculation of argument of 
     * minimum must be made during next call of {@link #getArgMin()}.
     */
    public void AfterConvexCostFunctionChanged() {            
        clearCache();
    }
    
    /**
     * Removes itself from all listener-lists.
     * @throws Throwable 
     */
    @Override
    public void finalize() throws Throwable{
        super.finalize();
        for(ConvexCostFunction convexCostFunction: convexCostFunctions){
           convexCostFunction.removeConvexCostFunctionListener(this);
        }
    }
    
    public List<ConvexCostFunction> getAllConvexCostFunctions(){
        List<ConvexCostFunction> functions = new ArrayList<ConvexCostFunction>();
        functions.addAll(convexCostFunctions);
        functions.add(quadraticCostFunction);
        return functions;
    }

    void addCostFunction(ConvexCostFunctionSum sum) {
        for(ConvexCostFunction fn: sum.getAllConvexCostFunctions()){
            addCostFunction(fn);
        }
    }
}
